
//set useful namespaces
$N("Archetype.Components", $H());



/**
 * @class Archetype.Component
 * Class for loading components
 */
$C = Archetype.Component = Archetype.Class.singleton(
/**
 * @scope Archetype.Component.prototype
 */
{    
    _storeDefinition: function(/*basic HashMap*/ definitionWithParents) {
    	if(!Archetype.TemporaryComponentDefinitions) {
    		Archetype.TemporaryComponentDefinitions = [];
    	}
    	Archetype.TemporaryComponentDefinitions.push(definitionWithParents);
    },
    
    create: function() {
    	if(Archetype.debugComponentResolver) {
    		console.warn("Archetype.Component.create ", arguments);
    	}
    	
    	if(arguments.length == 1) {
    		var definition = arguments[0];
    		Archetype.Component._storeDefinition({
    			parents: [],
    			definition: definition
    		});
    	} else if(arguments.length == 2) {
    		var parents = arguments[0];
    		var definition = arguments[1];
    		Archetype.Component._storeDefinition({
    			parents: parents,
    			definition: definition
    		});
    	} else {
    		Logger.warn("Archetype.Component.create is only defined for 1 or 2 parameters");
    	}
    },
    
    /**
     * @deprecated Use create(parents, definition) instead
     */
    Extend: function(/*Array*/ parents, /*HashMap*/ definition) {
    	Archetype.Component.create(parents, definition);
    },
    
    _resolveComponent: function(/*String*/ url) {
    	if(Archetype.debugComponentResolver) {
    		console.warn("Archetype.Component._resolveComponent ", arguments);
    	}
    	
    	while(Archetype.TemporaryComponentDefinitions.length > 0) {
    		var definitionWithParents = Archetype.TemporaryComponentDefinitions.pop();
    		
	    	var parentsNames = definitionWithParents.parents;
	    	var definition = definitionWithParents.definition;
	    	
	        var needToLoad = $A();
	
	        parentsNames.each(function(name) {
	            if(typeof Archetype.Components.get(name) != "function") {
	                needToLoad.push(name);
	            }
	        });
	        
	        Archetype.require(needToLoad, {
	            evaluation: "component",
	            callback: this._onParentsLoaded.bind(this, parentsNames, definition, url)
	        });
    	}
    },
    
    /**
     * get a Archetype.Component by it's UMN
     * @param {UMN or String} umn object or realPath
     * @return {Archetype.Component}
     */
    get: function(/*String*/ module) {
        return Archetype.Components.get(module);
    },
    /**
     * Build the extended component when Parents are loaded
     * @param {Array of String} parentsNames ex:["path.to.component1","path.to.component2"] latest override others in case of conflict
     * @param {HashMap} definition
     * @private
     */
    _onParentsLoaded: function(/*Array of String*/parentsNames, /*HashMap*/definition, /*URL*/ url) {
    	if(Archetype.debugComponentResolver) {
    		console.warn("Archetype.Component._onParentsLoaded ", arguments);
    	}
    	
    	if(typeof definition.name != "undefined") {
    		var name = definition.name;
    	} else {
    		var name = $$U(url);
    	}
    	
    	parentsNames.push("Archetype.component.abstractComponent");
    	
    	if(parentsNames.length > 0) {
	    	var finalDefinition = $H(definition);
	
	        //extend the definition with each parent definition
	        $A(parentsNames).each(function(parentName){
	            var parentDef = Archetype.ComponentDefinitions.get(parentName);
	            finalDefinition = $H(parentDef).merge(finalDefinition).toObject();
	            finalDefinitiontmp = finalDefinition;
	            finalDefinition = Object.deepExtend(parentDef,finalDefinitiontmp);
	        });
    	} else {
    		var finalDefinition = definition;
    	}
    	
    	finalDefinition.name = name; //finalDefinition can have parent name if children's name not defined
    	
        Archetype.ComponentDefinitions.set(name, finalDefinition);
        Archetype.ComponentManager.loadComponent/*DefinitionFromPath*/(name);
    }
}).instance();



/**
 * @class
 * Archetype.Component manager. Sets up components, load their dependencies, etc.
 * Should only be used through the convenient "Archetype.Component." wrappers
 */
Archetype.ComponentManager = Archetype.Class.singleton(
/** @scope Archetype.ComponentManager.prototype */
{
    /**
     * Setup Archetype.Component Management configuration
     */
    initialize: function() {
        Archetype.require("Archetype.component.abstractComponent", {asynchronous: false});
        this.methodManager= new MethodManager();
    },
    /**
     * Initialize the component definition.
     * Launch the loading of dependencies.
     * @param {String} module
     */
    loadComponent: function(/*String*/ name) {
    	if(Archetype.debugComponentResolver) {
    		console.warn("Archetype.ComponentManager.loadComponent ", arguments);
    	}
        
    	var componentDefinition = Archetype.ComponentDefinitions.get(name);

        componentDefinition.beforeInstanciate();
        
        if(typeof componentDefinition.setup == "undefined") {
        	componentDefinition.setup = {};
        }
        if(typeof componentDefinition.setup.dependencies == "undefined") {
        	componentDefinition.setup.dependencies = {};
        }
        
        componentDefinition.setup.dependencies = Object.extend({
        	components:{},
        	lib:[],
        	css:[],
        	html:{}
        }, componentDefinition.setup.dependencies);
        
        if(Archetype.debugComponentManager) {
        	Logger.debug("[COMPONENT] loadComponent start ", name);
        }
        
        var setupJoiner = new Archetype.Joiner(["components", "templates", "libs"], this.startComponent.bind(this, componentDefinition));

        //Components
        if(typeof componentDefinition.setup.dependencies.components == "undefined") {
        	componentDefinition.setup.dependencies.components = {};
        }
        componentDefinition.setup.dependencies.lazycomponents = {};
        $H(componentDefinition.setup.dependencies.components).each(function(component) {
        	var index = component.value.indexOf("@Lazy");
        	if(index >= 0) {
        		delete componentDefinition.setup.dependencies.components[component.key];
        		componentDefinition.setup.dependencies.lazycomponents[component.key] = component.value.replace("@Lazy ", "");
        	}
        });
		Archetype.require($H(componentDefinition.setup.dependencies.components).values(), {
            evaluation: "component",
            callback: this.componentsDependenciesLoaded.bind(this, componentDefinition, setupJoiner)
        });

        //Template system
        if(typeof componentDefinition.setup.dependencies.html != "undefined") {
            if(typeof componentDefinition.setup.template == "undefined") {
            	var wrapper = "Archetype.template." + Archetype.template + "Template";
            	componentDefinition.setup.dependencies.lib.push(wrapper);
            } else {
                if(componentDefinition.setup.template.indexOf("template.") == 0) {
                    //Hack because modules conf doesn't work fine for this need
                    componentDefinition.setup.template = "Archetype." + componentDefinition.setup.template;
                }
                var wrapper = "Archetype.template." + componentDefinition.setup.template + "Template";
                componentDefinition.setup.dependencies.lib.push(componentDefinition.setup.template);
            }
        }

        //Libraries
        Archetype.require(componentDefinition.setup.dependencies.lib, {
            evaluation: "js",
            asynchronous: true,
            callback: this.librariesDependenciesLoaded.bind(this, componentDefinition, setupJoiner)
        });

        //Styles
        if(typeof componentDefinition.setup.dependencies.css != "undefined") {
            Archetype.require(componentDefinition.setup.dependencies.css, {
                evaluation: "css",
                asynchronous: true
            });
        }
    },
    /**
     * Called when all component's components dependancies are available.
     * Add a HashMap with components constructors in the options of the joiner in order to adding them in the new component definition.
     * @param {HashMap} componentDefinition
     * @param {Archetype.Joiner} setupJoiner
     */
    componentsDependenciesLoaded: function(/*String []*/ componentDefinition, /*Archetype.Joiner*/ setupJoiner) {
    	var components = $H();

        $H(componentDefinition.setup.dependencies.components).each(function(pair) {
            components.set(pair.key, Archetype.Components.get(pair.value));
        });

        setupJoiner.getJoiner("components", {components: components.toObject()})();
        
        if(Archetype.debugComponentManager) {
        	Logger.debug("[COMPONENT] componentsDependenciesLoaded ", componentDefinition.name);
        }
    },
    /**
     * Called when all libraries are available.
     * We have to test if there is a specialized template system
     * @param {HashMap} componentDefinition
     * @param {Archetype.Joiner} setupJoiner
     */
    librariesDependenciesLoaded: function(/*String []*/ componentDefinition, /*Archetype.Joiner*/ setupJoiner) {
        setupJoiner.getJoiner("libs")();
		
        if(Archetype.debugComponentManager) {
        	Logger.debug("[COMPONENT] librariesDependenciesLoaded ", componentDefinition.name);
        }
        
        if(typeof componentDefinition.setup.dependencies.html != "undefined" || typeof componentDefinition.render == "function") {
            if(typeof componentDefinition.setup.template == "undefined") {
                componentDefinition.setup.templateSystem = Archetype.Template;
            } else {
                var templateName = componentDefinition.setup.template.replace(/^(Archetype\.)?template\./,"");
                templateName = templateName.substr(0, 1).toUpperCase() + templateName.substr(1);
                componentDefinition.setup.templateSystem = Archetype.Templates[templateName];
            }
        }
		
        if (typeof componentDefinition.setup.dependencies.html == "undefined") {
            setupJoiner.getJoiner("templates")();
        } else {
        	Archetype.Templates.require($H(componentDefinition.setup.dependencies.html).values(), this.templatesDependenciesLoaded.bind(this, componentDefinition, setupJoiner), componentDefinition.setup.templateSystem);
        }
    },
    /**
     * Called when all component's templates dependancies are available.
     * Add a HashMap with templates in the options of the joiner in order to adding them in the new component definition.
     * @param {HashMap} componentDefinition
     * @param {Archetype.Joiner} setupJoiner
     * @param {HashMap} templates
     */
    templatesDependenciesLoaded: function(/*String []*/ componentDefinition, /*Archetype.Joiner*/ setupJoiner) {
        var keys = $H(componentDefinition.setup.dependencies.html).keys();
        var paths = $H(componentDefinition.setup.dependencies.html).values();
		
        var templates = $H(templates);
        $A(paths).each(function(path, index) {
        	templates.set(keys[index], Archetype.Templates.get(path));
        });
        
        setupJoiner.getJoiner("templates", {templates: templates.toObject()})();
        
        if(Archetype.debugComponentManager) {
        	Logger.debug("[COMPONENT] templatesDependenciesLoaded ", componentDefinition.name);
        }
    },
    /**
     * Generate a component constructor from definition
     * @param {Object} componentDefinition
     */
    prototypizeComponent: function(_componentDefinition) {
        //Logger.debug("Creating a component");
        var _constructor= function() {
            componentDefinition = $H(_componentDefinition);
            var _private = this._private = {
                _definition: _componentDefinition,
                components: _componentDefinition.components,
                templates: _componentDefinition.templates
            }

            Object.extend(_private, Event.Publisher);
            Object.extend(_private, Event.Listener);

            //copy all variables from definition to private
            //treat all objects (including hashmaps and arrays) : clone
            /*componentDefinition
            .findAll(function(p){return typeof p.value != "Function" && typeof p.value != "String" && typeof p.value != "Number"})
            .each(function(p){_private[p.key] = Object.clone(p.value)});
            //treat all basic types : duplicate by copy
            componentDefinition
            .findAll(function(p){return typeof p.value == "String" || typeof p.value == "Number"})
            .each(function(p){_private[p.key] = p.value});*/
            //should correctly clone the  componentDefiniton Prototype Hashmap
            this._private=componentDefinition.merge(_private).toObject();
                                
            Archetype.ComponentManager.methodManager.buildMethods(this);

            if (componentDefinition.get("initialize")) {
                componentDefinition.get("initialize").apply(this._private, arguments);
            }
        };

        var definition= {
            initialize: _constructor
        };

        return Archetype.Class.create(definition);
    },
    /**
     * Initialize contructor for components with lazy loading
     * @param {Object} componentDefinition
     */
    initializeLazyComponents: function(componentDefinition) {
    	$H(componentDefinition.setup.dependencies.lazycomponents).each(function(component) {
    		var lazyConstructor = function() {
    			var constructorArguments = arguments;
    			Archetype.requireComponent(component.value, function(constructor) {
    				var proxyConstructor = Archetype.Class.create({
    		            initialize: constructor.prototype.initialize.apply(this, constructorArguments)
    		        });
    				
    				new proxyConstructor();
    			});
    		}
    		 
    		componentDefinition.components[component.key] = Archetype.Class.create({
    			initialize: lazyConstructor
    		});
    	});
    },
    /**
     * Called when all component's dependencies are available
     * Create the component and return the corresponding object
     * Run new component instance
     * @param {HashMap} componentDefinition
     * @param {HashMap} options options from the joiner, contain hashmaps for components and templates dependencies
     */
    startComponent: function(/*HashMap*/ componentDefinition, /*HashMap*/ options) {
        componentDefinition.components = options.get("components");
        componentDefinition.templates = options.get("templates");
        
        this.initializeLazyComponents(componentDefinition);
        
        var name = componentDefinition.name;
        
        if(Archetype.debugComponentManager) {
        	Logger.debug("[COMPONENT] before prototypize ", name);
        }

        Archetype.Components.set(name, this.prototypizeComponent(componentDefinition));

        if(Archetype.debugComponentManager) {
        	Logger.debug("[COMPONENT] after prototypize ", name);
        }
        
        Logger.debug("Component loaded : "+ name);
        Archetype.engine.fire("Component", name);
    }
}).instance();
